'''
Created on 24 Oct 2012

@author: quirino.brizi
'''
from Queue import Queue, Empty
from org.codesketch.loadit.configuration.domain import Configuration
from org.codesketch.loadit.decorator import accepts, returns, selfType
from org.codesketch.loadit.http import HttpResponse, WebClient
from threading import Thread

class Writer(Thread):
    '''
    Helper class, marshal and sends transaction results to LoadIT server using HTTP POST method. 
    '''

    @accepts(selfType, Configuration, debug=1)
    def __init__(self, configuration=None):
        '''
        Create a new writer instance initialising the internal data structures accordingly to the 
        given configuration
        @param configuration: the application configuration object
        '''
        Thread.__init__(self)
        self.__queue = Queue()
        # Configure HTTP client
        server = configuration.getServer()
        self.__client = WebClient(server.getBaseUrl());
        if server.isBasicAuth() :
            self.__client.setBasicAuthentication(server.getUsername(), server.getPassword());
        # Configure internal status
        self.__test = configuration.getTest().getIdentifier()
        self.__sampling = configuration.getTest().getSampling()
        self.__stop = False;
    
    def __del__(self) :
        self.__client.close()
        del self.__client
        del self.__queue
    
    @accepts(selfType, HttpResponse, debug=2)
    def add(self, httpResponse):
        '''
        Add a new HttpResponse to the processing queue
        @param httpResponse: the response to marshal and send to LoadIT server
        '''
        self.__queue.put(httpResponse, True, None)
    
    def stop(self):
        '''
        Stop the writer execution consuming all the responses that are stored on the queue
        '''
        self.__stop = True;
        
    def run(self):
        '''
        Main loop method
        '''
        index = 0
        while not self.__stop :
            
            try :
                response = self.__queue.get(True, 10)
                self.__queue.task_done()
                if index == self.__sampling - 1 and response :
                    if response :
                        self.send(self.marshall(response))
                else :
                    index = index + 1
                del response
            except Empty, _ : continue
    
    @accepts(selfType, HttpResponse, debug=2)
    @returns(str, debug=2)
    def marshall (self, httpResponse):
        '''
        Marshal the given httpResponse into XML
        '''
        info = httpResponse.info()
        xml = "<record><test>%s</test><url>%s</url><responseCode>%s</responseCode><latency>%f</latency><nameLookupTime>%f</nameLookupTime><connectTime>%f</connectTime><preTransferTime>%f</preTransferTime><startTransferTime>%f</startTransferTime><redirectTime>%f</redirectTime><redirectCount>%s</redirectCount><sizeDownload>%f</sizeDownload><contentType>%s</contentType><speedDownload>%f</speedDownload></record>" % (self.__test, info['effective-url'],info['response-code'],info['total-time'], info['namelookup-time'], info['connect-time'], info['pretransfer-time'],info['starttransfer-time'],info['redirect-time'],info['redirect-count'],info['size-download'],info['content-type'],info['speed-download'])
        return xml

    @accepts(selfType, str)
    def send(self, message):
        self.__client.post(None, None, message);
#        print message

class Runner(object):
    '''
    Test entry point, this class should be extended by the custom runners in order to 
    start the test and access common objects and configuration.
    '''
    
    @accepts(selfType, Configuration, Writer)
    def configure(self, conf, writer) :
        '''
        Initialises the runner object.
        '''
        self.__configuration = conf
        self.__writer = writer
      
    def _configuration(self): return self.__configuration
    def _writer(self): return self.__writer
    
    def execute(self):
        '''
        Execution entry point, this method should be overwritten by the custom runners.
        '''
        raise NotImplementedError()
    
class AsyncHTTPRequestHandler(Thread) :
    '''
    Asynchronously executes request against the target server.
    '''
    
    @accepts(selfType, int, Writer, debug=2)
    def __init__(self, size, writer) :
        '''
        Initialises the AsyncHTTRequestHandler.
        @param size: the internal WebClient pool size
        @param writer: the output writer that sends messages to LoadIT server
        '''
        Thread.__init__(self)
        self.__waiting = Queue()
        self.__running = Queue()
        self.__writer = writer
        
        for _ in range(size) :
            self.__waiting.put_nowait(WebClient())
        
        self.__stop = False
    
    def __del__(self): self.close()
    
    @accepts(selfType, str, str, dict, debug=2)
    def get(self, baseUrl, relativeUrl="", params=None):
        '''
        Handle a new HTTP GET request asynchronously. HTTP request will not be handled if stop has been requested
        @param baseUrl: the base URL
        @param relativeUrl: the relative URL default to empty string
        @param params: the query string parameters as dictionary, default to None
        '''
        if not self.__stop :
            # get a new WebClient
            wc = self.__waiting.get(True)
            self.__waiting.task_done()
            # configure the WebClient
            wc.setUrl(baseUrl)
            # execute the request asynchronously
            Thread(target=wc.get, args=(relativeUrl, params)).start()
            # Add the WebClient to the running list
            self.__running.put(wc)
    
    @accepts(selfType, str, str, dict, str, debug=2)
    def post(self, baseUrl, relativeUrl="", params=None, payload=""):
        '''
        Handle a new HTTP POST request asynchronously. HTTP request will not be handled if stop has been requested
        @param baseUrl: the base URL
        @param relativeUrl: the relative URL default to empty string
        @param params: the post fields as dictionary, default to None
        @param payload: the post payload as a string
        '''
        if not self.__stop :
            # get a new WebClient
            wc = self.__waiting.get(True)
            self.__waiting.task_done()
            # configure the WebClient
            wc.setUrl(baseUrl)
            # execute the request asynchronously
            Thread(target=wc.get, args=(relativeUrl, params, payload)).start()
            # Add the WebClient to the running list
            self.__running.put(wc)
    
    def stop(self) : 
        '''
        Stops the asynchronous execution
        '''
        self.__stop = True
    
    def close(self) : 
        self.__stop = True
        while not self.__waiting.empty() or not self.__running.empty() :
            try :
                self.__waiting.get_nowait().close()
            except : pass
            try :
                self.__running.get_nowait().close()
            except : pass
    
    def run(self):
        '''
        Thread main loop, checks the running WebClient status.
        
        TODO: on stop ignores running not collected responses collect before end
        '''
        while not self.__stop :
            try :
                wc = self.__running.get(timeout=10)
                self.__running.task_done()
                if wc.isReady() :
                    self.__writer.add(wc.response())
                    self.__waiting.put(wc)
                else :
                    self.__running.put(wc)
            except Empty : continue
        
        self.close()

class AsyncRequestGenerator(Thread):
    
    @accepts(selfType, Configuration)
    def __init__(self, conf):
        '''
        Initialise AsyncRequestGenerator
        @param conf: the test configuration 
        '''
        Thread.__init__(self)
        self.__conf = conf
        
    def _getConfiguration(self): return self.__conf
    
    def request(self): 
        '''
        Return the new request
        '''
        raise "Not implemented yet"
    def _generate(self):
        '''
        Main generation loop should be implemented by the custom request generator
        '''
        raise "Not implemented yet"
    
    def run(self): self._generate()